#include "actionfiltre.h"
#include "iostream"
#include "maqgraphicsview.h"

#include "mondialogueposterisation.h"
#include "mondialoguefloumoyennage.h"
#include "mondialoguefiltrepersonnalise.h"

ActionFiltre::ActionFiltre(QWidget * mw) : QWidget(mw)
{
    mainWindow  = (MainWindow *)mw;
}

void ActionFiltre::connect(Ui::MainWindow* ui) {

    QObject::connect(ui->actionNiveaux_de_gris, SIGNAL(triggered()), this, SLOT(traitementNiveauxDeGris()));
    QObject::connect(ui->actionNegatif, SIGNAL(triggered()), this, SLOT(traitementNegatif()));
    QObject::connect(ui->actionAdaptatif,SIGNAL(triggered()),this,SLOT(filtreAdaptatif()));
    QObject::connect(ui->actionMoyenne, SIGNAL(triggered()), this, SLOT(traitementFlouParMoyennage()));
    QObject::connect(ui->actionGaussien, SIGNAL(triggered()), this, SLOT(traitementFlouGaussien()));
    QObject::connect(ui->actionMedian, SIGNAL(triggered()), this, SLOT(traitementFlouMedian()));

    QObject::connect(ui->actionRoberts, SIGNAL(triggered()), this, SLOT(detectionContourRoberts()));
    QObject::connect(ui->actionSobel, SIGNAL(triggered()), this, SLOT(detectionContourSobel()));
    QObject::connect(ui->actionAvec_chainage, SIGNAL(triggered()), this, SLOT(sobelPlusChainage()));

    QObject::connect(ui->actionRehaussement_de_Contraste, SIGNAL(triggered()), this, SLOT(rehaussement()));
    QObject::connect(ui->actionPosterisation, SIGNAL(triggered()), this, SLOT(posterisation()));

    QObject::connect(ui->actionPersonnalise, SIGNAL(triggered()), this, SLOT(filtrePersonnalise()));
}

ActionFiltre::~ActionFiltre(){

}

FenetreImage * ActionFiltre::getFenetreImageCourante(){
    QMdiSubWindow * sousFenetre   = mainWindow->getMdiArea()->activeSubWindow();
    if(sousFenetre == NULL){ return NULL; }

    return (FenetreImage *)(sousFenetre->widget());
}

void ActionFiltre::traitementNiveauxDeGris(){
    FenetreImage *fenetreImage = this->getFenetreImageCourante();
    if(fenetreImage == NULL){ return; }

    Calque *copie = fenetreImage->getCalqueCourant()->copie();

    ElementHistorique* eh = new ElementHistorique(QString("niveau de gris"), copie);
    fenetreImage->historique.ajouter(eh);

    QImage * imageATraiter  = (QImage *)(fenetreImage->getCalqueCourant());

    this->imageEnNiveauxDeGris(imageATraiter);

    fenetreImage->a_ete_modifie=true;
    fenetreImage->updateCalqueCourant();
}

void ActionFiltre::imageEnNiveauxDeGris(QImage * imageDepart){
    QRgb couleur;
    uint gris;
    // on boucle sur tous les pixels
    int i;
    int j;
    // pour toutes les lignes
    for(i=0; i<imageDepart->height(); i++){
        // pour toutes les colonnes
        for(j=0; j<imageDepart->width(); j++){
            // on prend la couleur
            couleur = imageDepart->pixel(j, i);
            // on calcule la luminance
            gris    = 0.299*qRed(couleur)
                    + 0.587*qGreen(couleur)
                    + 0.114*qBlue(couleur);
            couleur = qRgba(gris,gris,gris,qAlpha(couleur));
            imageDepart->setPixel(j,i,couleur);
        }
    }
}

void ActionFiltre::traitementNegatif(){
    FenetreImage *fenetreImage = this->getFenetreImageCourante();
    if(fenetreImage == NULL){ return; }

    Calque *copie = fenetreImage->getCalqueCourant()->copie();

    ElementHistorique* eh = new ElementHistorique(QString("negatif"), copie);
    fenetreImage->historique.ajouter(eh);

    Calque * imageATraiter  = fenetreImage->getCalqueCourant();
    QRgb couleur;
    // on boucle sur tous les pixels
    int i;
    int j;
    // pour toutes les lignes
    for(i=0; i<imageATraiter->height(); i++){
        // pour toutes les colonnes
        for(j=0; j<imageATraiter->width(); j++){
            // on prend la couleur
            couleur = imageATraiter->pixel(j, i);
            QRgb rgba = qRgba(255-qRed(couleur), 255-qGreen(couleur), 255-qBlue(couleur), qAlpha(couleur));
            imageATraiter->setPixel(j, i, rgba);
        }
    }
    fenetreImage->a_ete_modifie=true;
    fenetreImage->updateCalqueCourant();
}

void ActionFiltre::traitementFlouParMoyennage(){
    FenetreImage *fenetreImage = this->getFenetreImageCourante();
    if(fenetreImage == NULL){ return; }

    // on demande la taille du noyau a l'utilisateur
    MonDialogueFlouMoyennage * monDialogue = new MonDialogueFlouMoyennage(this);

    int acceptation = monDialogue->exec();

    if(acceptation == QDialog::Accepted){
        Calque *copie = fenetreImage->getCalqueCourant()->copie();

        ElementHistorique* eh = new ElementHistorique(QString("flou Moyen"), copie);
        fenetreImage->historique.ajouter(eh);

        QImage * imageATraiter  = (QImage *)(fenetreImage->getCalqueCourant());
        this->imageFlouteeParMoyennage(imageATraiter, monDialogue->getTailleNoyau());

        fenetreImage->a_ete_modifie=true;
        fenetreImage->updateCalqueCourant();
    }

    delete monDialogue;
}

void ActionFiltre::imageFlouteeParMoyennage(QImage *imageDepart, int tailleNoyau){
    Matrices * noyau    = Matrices::getNoyauMoyenneCarreDeTaille(tailleNoyau);

    Matrices::convolution(imageDepart, noyau);
}

void ActionFiltre::traitementFlouGaussien(){
    FenetreImage *fenetreImage = this->getFenetreImageCourante();
    if(fenetreImage == NULL){ return; }
        // on demande la taille du noyau a l'utilisateur
        MonDialogueFlouMoyennage * monDialogue = new MonDialogueFlouMoyennage(this);

        int acceptation = monDialogue->exec();
        if(acceptation == QDialog::Accepted){
        Calque *copie = fenetreImage->getCalqueCourant()->copie();

        ElementHistorique* eh = new ElementHistorique(QString("flou Gaussien"), copie);
        fenetreImage->historique.ajouter(eh);

        Matrices * noyau    = Matrices::getNoyauGaussien(monDialogue->getTailleNoyau());
        Calque * image      = fenetreImage->getCalqueCourant();

        Matrices::convolution((QImage *)image, noyau);

        fenetreImage->a_ete_modifie=true;
        fenetreImage->updateCalqueCourant();
    }
    delete monDialogue;
}

void ActionFiltre::detectionContourRoberts(){
    FenetreImage *fenetreImage = this->getFenetreImageCourante();
    if(fenetreImage == NULL){ return; }

    Calque * imageATraiter  = fenetreImage->getCalqueCourant();

    Calque *copie = fenetreImage->getCalqueCourant()->copie();
    ElementHistorique* eh = new ElementHistorique(QString("contour Roberts"), copie);
    fenetreImage->historique.ajouter(eh);

    // on fabrique les noyaux de sobel en x et en y
    Matrices * sobelX   = Matrices::getNoyauRobertsKernelX();
    Matrices * sobelY   = Matrices::getNoyauRobertsKernelY();

    QImage * imageGrise = new QImage(imageATraiter->copy());
    this->imageEnNiveauxDeGris(imageGrise);
    this->imageFlouteeParMoyennage(imageGrise, 3);

    // on prepare les matrices comportants les valeurs calculees
    Matrices * gradientX    = Matrices::convolutionGrise(imageGrise, sobelX, 1.0);
    Matrices * gradientY    = Matrices::convolutionGrise(imageGrise, sobelY, 1.0);

    // on calcule les valeurs des gradients
    Matrices * gradientNorme= new Matrices(gradientX->getHauteur(), gradientX->getLargeur());
    int tmpX        = gradientX->getValeurDansCase(1, 1);
    int tmpY        = gradientY->getValeurDansCase(1, 1);
    int gradientMin = sqrt(tmpX*tmpX + tmpY*tmpY);
    int gradientMax = gradientMin;

    int valeur;
    // on calcule la norme
    for(int i=1; i<gradientX->getHauteur()-1; i++){
        for(int j=1; j<gradientX->getLargeur()-1; j++){
            tmpX    = gradientX->getValeurDansCase(i, j);
            tmpY    = gradientY->getValeurDansCase(i, j);
            valeur  = sqrt(tmpX*tmpX + tmpY*tmpY);
            gradientNorme->setValeurDansCase(i, j, valeur);

            // on met a jour les min et max
            if(valeur < gradientMin){
                gradientMin = valeur;
            }else if(valeur > gradientMax){
                gradientMax = valeur;
            }
        }
    }

    // puis on recadre
    QRgb couleur;
    int gris;
    for(int i=1; i<gradientNorme->getHauteur()-1; i++){
        for(int j=1; j<gradientNorme->getLargeur()-1; j++){
            gris    = gradientNorme->getValeurDansCase(i, j);
            gris    = 255*(gris-gradientMin) / (gradientMax-gradientMin);
            couleur = qRgba(gris, gris, gris, qAlpha(imageATraiter->pixel(j, i)));

            imageATraiter->setPixel(j, i, couleur);
        }
    }
    delete imageGrise;
    delete gradientNorme;
    fenetreImage->a_ete_modifie=true;
    fenetreImage->updateCalqueCourant();
}

void ActionFiltre::contoursSobel(QImage *imageDepart){
    // on fabrique les noyaux de sobel en x et en y
    Matrices * sobelX   = Matrices::getNoyauSobelKernelX();
    Matrices * sobelY   = Matrices::getNoyauSobelKernelY();

    QImage * imageGrise = new QImage(imageDepart->copy());
    ActionFiltre::imageEnNiveauxDeGris(imageGrise);

    // on prepare les matrices comportants les valeurs calculees
    Matrices * gradientX    = Matrices::convolutionGrise(imageGrise, sobelX, 4.0);
    Matrices * gradientY    = Matrices::convolutionGrise(imageGrise, sobelY, 4.0);

    // on calcule les valeurs des gradients
    Matrices * gradientNorme= new Matrices(gradientX->getHauteur(), gradientX->getLargeur());
    int tmpX        = gradientX->getValeurDansCase(1, 1);
    int tmpY        = gradientY->getValeurDansCase(1, 1);
    int gradientMin = sqrt(tmpX*tmpX + tmpY*tmpY);
    int gradientMax = gradientMin;

    int valeur;
    // on calcule la norme
    for(int i=1; i<gradientX->getHauteur()-1; i++){
        for(int j=1; j<gradientX->getLargeur()-1; j++){
            tmpX    = gradientX->getValeurDansCase(i, j);
            tmpY    = gradientY->getValeurDansCase(i, j);
            valeur  = sqrt(tmpX*tmpX + tmpY*tmpY);
            gradientNorme->setValeurDansCase(i, j, valeur);

            // on met a jour les min et max
            if(valeur < gradientMin){
                gradientMin = valeur;
            }else if(valeur > gradientMax){
                gradientMax = valeur;
            }
        }
    }

    // puis on recadre
    QRgb couleur;
    int gris;
    for(int i=1; i<gradientNorme->getHauteur()-1; i++){
        for(int j=1; j<gradientNorme->getLargeur()-1; j++){
            gris    = gradientNorme->getValeurDansCase(i, j);
            gris    = std::min(255, std::max(0, gris));


            couleur = qRgba(gris, gris, gris, qAlpha(imageDepart->pixel(j, i)));

            imageDepart->setPixel(j, i, couleur);
        }
    }

    delete gradientNorme;
    delete imageGrise;
}

void ActionFiltre::detectionContourSobel(){
    FenetreImage *fenetreImage = this->getFenetreImageCourante();
    if(fenetreImage == NULL){ return; }

    Calque * imageATraiter  = fenetreImage->getCalqueCourant();

    Calque *memoire = fenetreImage->getCalqueCourant()->copie();

    ElementHistorique* eh = new ElementHistorique(QString("contour Sobel"), memoire);
    fenetreImage->historique.ajouter(eh);

    this->contoursSobel(imageATraiter);

    fenetreImage->a_ete_modifie=true;
    fenetreImage->updateCalqueCourant();
}

void ActionFiltre::sobelPlusChainage(){
    FenetreImage *fenetreImage = this->getFenetreImageCourante();
    if(fenetreImage == NULL){ return; }

    Calque * imageATraiter  = fenetreImage->getCalqueCourant();

    // on garde en memoire l'ancienne image

    Calque *memoire = fenetreImage->getCalqueCourant()->copie();
    ElementHistorique* eh = new ElementHistorique(QString("Sobel et Chainage"), memoire);
    fenetreImage->historique.ajouter(eh);

    // on detecte les contours sur une nouvelle image
    QImage * copie  = new QImage(imageATraiter->copy());
    ActionFiltre::contoursSobel(copie);

    // suite : maxima locaux + chainage
    // a completer

    fenetreImage->a_ete_modifie=true;
    fenetreImage->updateCalqueCourant();
}


void ActionFiltre::rehaussement(){
    FenetreImage *fenetreImage = this->getFenetreImageCourante();
    if(fenetreImage == NULL){ return; }

    Calque * imageATraiter  = fenetreImage->getCalqueCourant();

    Calque *copie = imageATraiter->copie();
    ElementHistorique* eh = new ElementHistorique(QString("rehaussement Contraste"), copie);
    fenetreImage->historique.ajouter(eh);

    Matrices * noyau = new Matrices(3, 3);
    noyau->setValeurDansCase(0,0, 0);
    noyau->setValeurDansCase(0,1, -1);
    noyau->setValeurDansCase(0,2, 0);
    noyau->setValeurDansCase(1,0, -1);
    noyau->setValeurDansCase(1,1, 5);
    noyau->setValeurDansCase(1,2, -1);
    noyau->setValeurDansCase(2,0, 0);
    noyau->setValeurDansCase(2,1, -1);
    noyau->setValeurDansCase(2,2, 0);

    Matrices::convolution(imageATraiter, noyau, 1);
    fenetreImage->a_ete_modifie=true;
    fenetreImage->updateCalqueCourant();

    delete noyau;
}

void ActionFiltre::posterisation() {
    FenetreImage *fenetreImage = this->getFenetreImageCourante();
    if(fenetreImage == NULL){ return; }

    // on demande la taille du noyau a l'utilisateur
    MonDialoguePosterisation * monDialogue = new MonDialoguePosterisation(fenetreImage);

    monDialogue->show();

    cout << "coutcout" << endl;
}

void ActionFiltre::filtreAdaptatif() {
    FenetreImage *fenetreImage = this->getFenetreImageCourante();
    if(fenetreImage == NULL){ return; }

    MonDialogueFlouMoyennage * monDialogue = new MonDialogueFlouMoyennage(this);

    int acceptation = monDialogue->exec();

    if(acceptation == QDialog::Accepted){

        Calque *copie = fenetreImage->getCalqueCourant()->copie();
        ElementHistorique* eh = new ElementHistorique(QString("filtre Adaptatif"), copie);
        fenetreImage->historique.ajouter(eh);

        Calque* calque = fenetreImage->getCalqueCourant();
        QImage* copy = new QImage(fenetreImage->getCalqueCourant()->copy());
        int hauteur = calque->height();
        int largeur = calque->width();
        int taille=monDialogue->getTailleNoyau();

        int toto=(taille-1)/2;
        QRgb* sousImage = new QRgb[taille*taille];

        for(int y=toto; y< hauteur-toto; ++y) {
            //uint* line = (uint*) calque->scanLine(y);
            for(int x=toto; x<largeur-toto; ++x) {
                // recupère sous image
                for(int i=0; i<taille; ++i) {
                    for(int j=0; j<taille; ++j) {
                        sousImage[i*taille+j]= calque->pixel(x-toto+j,y-toto+i);
                    }
                }
                // creation du filtre
                int res =adaptatif(sousImage, taille*taille);
                copy->setPixel(x,y, qRgb(res,res,res));
            }
        }

        QPainter p(calque);
        p.drawImage(0,0, *copy);
        fenetreImage->updateCalqueCourant();
        delete copy;
    }
    delete monDialogue;
}

int ActionFiltre::adaptatif(QRgb* sousImage, int taille) {
    float* tab = new float[taille];
    float norme = 0;
    int valMilieu = qRed(sousImage[taille/2]);
    int superPixel = 0;

    for(int i=0; i<taille; i++) {
        tab[i] = (float) abs((int) (valMilieu-qRed(sousImage[i])));
        if(tab[i] == 0) {
            tab[i] = 5;
        }
        norme += (float) 1.0/tab[i];
    }

    for(int i=0; i < taille;i++) {
        //superPixel += (float) (qRed(sousImage[i]) * ((1.0/tab[i])/norme));
        superPixel += (int) ( float(qRed(sousImage[i])) * ((1.0/tab[i])/norme));
    }

    delete[] tab;
    return superPixel;
}


void ActionFiltre::filtrePersonnalise(){
    FenetreImage *fenetreImage = this->getFenetreImageCourante();
    if(fenetreImage == NULL){ return; }

    Calque *memoire = fenetreImage->getCalqueCourant()->copie();
    ElementHistorique* eh   = new ElementHistorique(QString("filtre Personnalise"), memoire);
    fenetreImage->historique.ajouter(eh);

    // on demande la taille du noyau a l'utilisateur
    MonDialogueFiltrePersonnalise * monDialogue = new MonDialogueFiltrePersonnalise(this);
    int acceptation = monDialogue->exec();

    if(acceptation == QDialog::Accepted){
        QImage * imageATraiter  = (QImage *)(fenetreImage->getCalqueCourant());

        Matrices * noyau  = monDialogue->getNoyauPerso();

        Matrices::convolution(imageATraiter, noyau);

        fenetreImage->a_ete_modifie=true;
        fenetreImage->updateCalqueCourant();
    }

    delete monDialogue;
}


void ActionFiltre::traitementFlouMedian(){
    FenetreImage *fenetreImage = this->getFenetreImageCourante();
    if(fenetreImage == NULL){ return; }

    Calque *memoire = fenetreImage->getCalqueCourant()->copie();
    ElementHistorique* eh   = new ElementHistorique(QString("flou Median"), memoire);
    fenetreImage->historique.ajouter(eh);

    Matrices::convolutionMedianne((QImage *)(fenetreImage->getCalqueCourant()));

    fenetreImage->a_ete_modifie=true;
    fenetreImage->updateCalqueCourant();
}
